package com.ncell.cell;

import com.ncell.utils.Utils;
import java.util.ArrayList;
import java.util.List;

public class UnitProcessor {

    private List<I> is;
    private List<P> ps;
    private List<I> TPlist;
    private PIUnit unit;
    private final List<P> activePs = new ArrayList<>();
    private final List<I> toBeConvertedToPermanent = new ArrayList<>(10);
    private final List<I> toAddToIsList = new ArrayList<>(10);
    private I lastConvertedI;
    private final float convertanceThreshold = 20f;

    public void setPUnit(PIUnit unit) {
        this.unit = unit;
        is = unit.getIs();
        ps = unit.getPs();
        TPlist = unit.getTPlist();
    }

    public void processStep(int stepsCount) {
        for (int i = 0; i < stepsCount; i++) {
            processStep();
        }
    }

    private List<P> getActivePs() {
        activePs.clear();
        for (P p : ps) {
            if (p.isPActive()) {
                activePs.add(p);
            }
        }
        return activePs;
    }

    private void beforeProcess() {
        for (P p : ps) {
            p.setCurrentMaxOarknOfConnectedI(0);
            p.setConnectedToActive(false);
        }

        for (I i : is) {
            i.setCurrentBestTemporalExtension(null);
            i.setOarkn(0);
            i.setActive(false);
        }

        for (I tp : TPlist) {
            tp.setOarkn(0);
            tp.setActive(false);
        }
    }

    private float IActivationCalculation(I i) {
        if (i.getConnections().isEmpty()) {
            return 0;
        }

        float activeCount = 0;
        for (PConnection connection : i.getConnections()) {
            if (connection.isPActive()) {
                activeCount++;
            }
        }

        int connectionsCount = i.getConnections().size();
        float oarkn = (activeCount / connectionsCount) * activeCount;
        TemporalExtension bestExtension = null;
        if (i.isPermanentI()) {
            I pi = (I) i;
            List<TemporalExtension> extensionList = pi.getTemporalExtensionList();
            if (extensionList != null && !extensionList.isEmpty()) {
                float bestOarkn = oarkn;
                for (TemporalExtension ext : extensionList) {
                    int totalPsMatched = 0;
                    int totalExtendedPs = 0;
                    if (ext.getPositiveList() != null && !ext.getPositiveList().isEmpty()) {
                        for (PConnection c : ext.getPositiveList()) {
                            if (c.isPActive()) {
                                totalPsMatched++;
                            }
                        }

                        totalExtendedPs += ext.getPositiveList().size();
                    }

                    if (ext.getNegativeList() != null && !ext.getNegativeList().isEmpty()) {
                        for (PConnection c : ext.getNegativeList()) {
                            if (!c.isPActive()) {
                                totalPsMatched++;
                            }
                        }

                        //negative list should not extends totalExtendedPs, because this pin already contains in ps list of I
                    }

                    if (totalExtendedPs != 0 || totalPsMatched != 0) {
                        float newActiveCount = (activeCount + totalPsMatched);
                        float tOarkn = (newActiveCount / (connectionsCount + totalExtendedPs)) * newActiveCount;
                        if (tOarkn > bestOarkn) {
                            bestOarkn = tOarkn;
                            bestExtension = ext;
                        }
                    }
                }

                if (bestExtension != null) {
                    if (bestOarkn > oarkn) {
                        oarkn = bestOarkn;
                        pi.setCurrentBestTemporalExtension(bestExtension);
                    } else {
                        bestExtension = null;
                    }
                }
            }
        }

        i.setOarkn(oarkn);
        for (PConnection connection : i.getConnections()) {
            P p = connection.getP();
            if (p.getCurrentMaxOarknOfConnectedI() < oarkn) {
                p.setCurrentMaxOarknOfConnectedI(oarkn);
            }
        }

        if (bestExtension != null) {
            if (bestExtension.getPositiveList() != null) {
                for (PConnection c : bestExtension.getPositiveList()) {
                    P p = c.getP();
                    if (p.getCurrentMaxOarknOfConnectedI() < oarkn) {
                        p.setCurrentMaxOarknOfConnectedI(oarkn);
                    }
                }
            }
            //we should not set oarkn to negative list, because all negative Ps were already processed with processing of basic ps
        }

        return oarkn;
    }

    private float iActivationCalculationPhase2(I i) {
        if (i.getConnections().isEmpty()) {
            return 0;
        }

        float activeCount = 0;
        float oarkn = i.getOarkn();
        for (PConnection connection : i.getConnections()) {
            P p = connection.getP();
            if (p.isPActive() && p.getCurrentMaxOarknOfConnectedI() <= oarkn) {
                activeCount++;
            }
        }

        int connectionsCount = i.getConnections().size();
        float coeficient = activeCount / connectionsCount;
        oarkn = coeficient * activeCount;
        boolean virp = false;
        if (i.isPermanentI()) {
            if (i.getCurrentBestTemporalExtension() != null) {
                float tOarkn = 0;
                float tCoeficient = 0;
                TemporalExtension ext = i.getCurrentBestTemporalExtension();
                int totalPsMatched = 0;
                int totalExtendedPs = 0;
                if (ext.getPositiveList() != null && !ext.getPositiveList().isEmpty()) {
                    for (PConnection c : ext.getPositiveList()) {
                        if (c.isPActive()) {
                            totalPsMatched++;
                        }
                    }

                    totalExtendedPs += ext.getPositiveList().size();
                }

                if (ext.getNegativeList() != null && !ext.getNegativeList().isEmpty()) {
                    for (PConnection c : ext.getNegativeList()) {
                        if (!c.isPActive()) {
                            totalPsMatched++;
                        }
                    }

                    //negative list should not extends totalExtendedPs, because this pin already contains in ps list of I
                }

                if (totalExtendedPs != 0 || totalPsMatched != 0) {
                    tCoeficient = (activeCount + totalPsMatched) / (connectionsCount + totalExtendedPs);
                    tOarkn = tCoeficient * (activeCount + totalPsMatched);
                }

                if (tOarkn > oarkn) {
                    oarkn = tOarkn;
                    coeficient = tCoeficient;
                    virp = true;
                }
            }
        }

        i.setOarkn(oarkn);
        i.setActive(coeficient >= 0.8);
        boolean fork = false;
        if (i.isActive()) {
            if (i.isPermanentI()) {
                if (i.getCurrentBestTemporalExtension() != null) {
                    TemporalExtension te = i.getCurrentBestTemporalExtension();
                    te.incrementActivationCounter();
                    if (te.getActivationCounter() > 30) {
                        fork = forkTemporalExtensionToPermanent(i, te);
                        oarkn = 0;
                    }

                    decrementActivationFromTemporalExtensionExceptActive(i, te);
                } else {
                    decrementActivationFromTemporalExtensionExceptActive(i, null);
                }
            }

            i.setActivateCounter(i.getActivateCounter() + 1.0f);
            System.out.println("I act=" + i.getActivateCounter());
            if (!i.isPermanentI() && i.getActivateCounter() > convertanceThreshold) {
                asyncConvertToPermanent(i);
            }

            if (!fork) {
                i.setAllPsChangeStateToConnectedToActive();
                if (virp) {
                    ((I) i).getCurrentBestTemporalExtension().setAllPsConnectedToActiveI();
                }
            }
        } else {
            i.setActivateCounter(i.getActivateCounter() * 0.99f);
        }

        return oarkn;
    }

    private void decrementActivationFromTemporalExtensionExceptActive(I i, TemporalExtension bestExtension) {
        for (TemporalExtension ext : i.getTemporalExtensionList()) {
            if (ext != bestExtension) {
                ext.decrementActivationCounter();
            }
        }
    }

    private void asyncConvertToPermanent(I i) {
        toBeConvertedToPermanent.add(i);
    }

    private void tryAddVirp(I bestI) {
        List<PConnection> positiveVirp = null;
        List<PConnection> negativeVirp = null;
        for (P p : activePs) {
            if (!p.isConnectedToActive()) {
                if (positiveVirp == null) {
                    positiveVirp = new ArrayList<>();
                }

                positiveVirp.add(new PConnection(p));
                p.setConnectedToActive(true);
            }
        }

        if (bestI.getCurrentBestTemporalExtension() != null) {
            TemporalExtension bestExt = bestI.getCurrentBestTemporalExtension();
            for (PConnection connection : bestI.getConnections()) {
                if (!connection.isPActive()) {
                    if (!bestExt.containsNegative(connection.getP())) {
                        if (negativeVirp == null) {
                            negativeVirp = new ArrayList<>();
                        }

                        negativeVirp.add(new PConnection(connection.getP()));
                    }
                }
            }
        } else {
            for (PConnection connection : bestI.getConnections()) {
                if (!connection.isPActive()) {
                    if (negativeVirp == null) {
                        negativeVirp = new ArrayList<>();
                    }

                    negativeVirp.add(new PConnection(connection.getP()));
                }
            }
        }

        if (positiveVirp != null || negativeVirp != null) {
            TemporalExtension te = new TemporalExtension();
            if (negativeVirp != null) {
                te.setNegativeList(negativeVirp);
            }
            if (positiveVirp != null) {
                te.setPositiveList(positiveVirp);
            }
            if (bestI.getTemporalExtensionList().size() >= Settings.extensionsListCount) {
                int worstIndex = bestI.getWorstTemporalExtensionIndex();
                if (worstIndex != -1) {
                    bestI.getTemporalExtensionList().remove(worstIndex);
                }
            }

            bestI.getTemporalExtensionList().add(te);
        }
    }

    private void processTPs(List<P> activePs) {
        List<P> notConnectedPs = null;
        for (P p : activePs) {
            if (!p.isConnectedToActive()) {
                if (notConnectedPs == null) {
                    notConnectedPs = new ArrayList<>();
                }

                notConnectedPs.add(p);
                //as we create new TP, all "notConnected" ps will change status to "connected", because we created new TP with this ps
                p.setConnectedToActive(true);
            }
        }

        if (notConnectedPs != null) {
            float centerX = 0;
            float centerY = 0;
            for (P p : notConnectedPs) {
                centerX += p.getX();
                centerY += p.getY();
            }
            centerX /= (float) notConnectedPs.size();
            centerY /= (float) notConnectedPs.size();
            centerX += Utils.getRandom(-3, 1);
            centerY += Utils.getRandom(-1, 2);
            if (centerX < 0) {
                centerX = 0;
            }
            if (centerY < 0) {
                centerY = 0;
            }

            //we shoud create new TP
            I tp;
            if (TPlist.size() >= Settings.tpListMaxSize) {
                tp = getWorstTp(TPlist);
                tp.clear();
            } else {
                tp = new I(false);
            }
            tp.addPs(notConnectedPs);
            tp.setX(centerX);
            tp.setY(centerY);
            tp.setActive(true);
            tp.setOarkn(notConnectedPs.size());
            TPlist.add(tp);
        }
    }

    private I getWorstTp(List<I> tpList) {
        if (tpList.isEmpty()) {
            return null;
        }
        I worstTP = tpList.get(0);
        float worstActivation = worstTP.getActivateCounter();
        for (I tp : tpList) {
            if (tp.getActivateCounter() < worstActivation) {
                worstTP = tp;
                worstActivation = tp.getActivateCounter();
            }
        }
        return worstTP;
    }

    private void processIs() {
        //на первом этапе происходит вычисление уровня активации данного И
        for (I i : Utils.concatenate(is, TPlist)) {
            IActivationCalculation(i);
        }

        I bestI = null;
        float bestOarkn = 0;
        for (I i : Utils.concatenate(is, TPlist)) {
            float oarkn = iActivationCalculationPhase2(i);
            if (lastConvertedI != null) {
                float toarkn = iActivationCalculationPhase2(lastConvertedI);
                if (lastConvertedI.isPermanentI() && lastConvertedI.isActive()) {
                    if (toarkn > bestOarkn) {
                        bestI = (I) lastConvertedI;
                        bestOarkn = toarkn;
                    }
                }

                toAddToIsList.add(lastConvertedI);
                lastConvertedI = null;
            } else {
                if (i.isPermanentI() && i.isActive()) {
                    if (oarkn > bestOarkn) {
                        bestI = (I) i;
                        bestOarkn = oarkn;
                    }
                }
            }
        }

        if (bestI != null) {
            tryAddVirp(bestI);
        }

        if (!toBeConvertedToPermanent.isEmpty()) {
            convertToPermanent();
        }

        if (!toAddToIsList.isEmpty()) {
            for (I i : toAddToIsList) {
                is.add(i);
            }
            toAddToIsList.clear();
        }
    }

    private void convertToPermanent() {
        for (I i : toBeConvertedToPermanent) {
            i.convertToPermanent();
            TPlist.remove(i);
            is.add(i);
        }

        toBeConvertedToPermanent.clear();
    }

    private boolean forkTemporalExtensionToPermanent(I i, TemporalExtension te) {
        i.getTemporalExtensionList().remove(te);
        //check if we should fork extension to new I, or just merge in current
        boolean fork = false;
        for (TemporalExtension e : i.getTemporalExtensionList()) {
            float act = e.getActivationCounter();
            if (act > 10) {
                fork = true;
                break;
            }
        }

        if (fork == false) {
            if (te.getActivationCounter() < te.getNotActivationCounter() * 1.5f) {
                fork = true;
            }
        }

        if (fork) {
            I forkI = new I(true);
            forkI.setX(i.getX() + Utils.getRandom(-2f, 2f));
            forkI.setY(i.getY() + Utils.getRandom(-2f, 2f));
            forkI.setActive(true);
            forkI.setOarkn(i.getOarkn());
            i.setActive(false);
            i.setOarkn(i.getOarkn() - 1);
            List<PConnection> connections = forkI.getConnections();
            //copy all except negative P(of course if we have negative list)
            if (te.getNegativeList() != null) {
                for (PConnection c : i.getConnections()) {
                    if (!te.containsNegative(c.getP())) {
                        connections.add(new PConnection(c.getP()));
                    }
                }
            } else {
                for (PConnection c : i.getConnections()) {
                    connections.add(new PConnection(c.getP()));
                }
            }
            if (te.getPositiveList() != null) {
                for (PConnection c : te.getPositiveList()) {
                    connections.add(new PConnection(c.getP()));
                }
            }

            lastConvertedI = forkI;
        } else {//merge
            List<PConnection> connections = i.getConnections();
            if (te.getNegativeList() != null) {
                for (int j = 0; j < connections.size(); j++) {
                    if (te.containsNegative(connections.get(j).getP())) {
                        connections.remove(j);
                        j--;
                    }
                }
            }

            if (te.getPositiveList() != null) {
                //TODO:make check not to add P from positiveList to connections if this P already in connections list
                for (PConnection c : te.getPositiveList()) {
                    connections.add(new PConnection(c.getP()));
                }
            }
        }

        return fork;
    }

    private void activateContextual() {
        for (P p : ps) {
            float tActivation = p.getActivationOffset();
            if (tActivation > 0) {
                tActivation -= 0.1;
                if (tActivation < 0) {
                    tActivation = 0;
                }

                p.setActivationOffset(tActivation);
            }
        }

        for (AbstractI i : is) {
            if (i.isActive()) {
                i.increaseActivationOffsetOnPs();
            }
        }

        for (AbstractI i : TPlist) {
            if (i.isActive()) {
                i.increaseActivationOffsetOnPs();
            }
        }
    }

    public void processStep() {
        beforeProcess();
        getActivePs();
        processIs();
        processTPs(activePs);
        activateContextual();
    }
}
